Skip to content

fix(project): Add polling fallback for file watching in containers - #1511

Draft
RandomByte wants to merge 1 commit into
mainfrom
fix/fs-watch-in-containers
Draft

fix(project): Add polling fallback for file watching in containers#1511
RandomByte wants to merge 1 commit into
mainfrom
fix/fs-watch-in-containers

Conversation

@RandomByte

@RandomByte RandomByte commented Aug 7, 2026

Copy link
Copy Markdown
Member

In many container setups (Docker over overlayfs, bind mounts, network filesystems) Linux inotify either does not deliver events or the native @parcel/watcher thread dies on EINTR (a SIGCHLD from a child process interrupts the blocking poll(), which the watcher treats as fatal, see parcel-bundler/watcher#253). In both cases subscribe() resolves and then stays silent, with no error to catch. The incremental build derives "what changed" solely from watcher events, so a silent watcher breaks rebuilds and live reload entirely.

Parcel offers no polling subscription, so add one in a new pollingWatcher.js module that all three watcher consumers now call instead of @parcel/watcher directly. It exposes a subscribe() matching @parcel/watcher's exact contract. The backend is chosen once per process: UI5_WATCH_MODE=polling|native forces it, otherwise a startup test subscribes with the native watcher to a temp dir on the same filesystem as the real watch, writes a file, and falls back to polling if no event arrives within 1500 ms.

The polling path walks the tree and diffs an mtimeMs+size snapshot every 250 ms (rescheduling itself after each walk so a slow crawl cannot overlap the next), emitting the same {type, path} events the native watcher would. Errors flow through the callback so each consumer's existing recovery path fires unchanged. The 250 ms interval stays below WATCHER_BURST_SETTLE_MS (550 ms) so downstream event batching still holds. Ignore globs reuse micromatch and prune ignored directories so node_modules is never crawled.

WatchHandler, ProjectDefinitionWatcher, and projectGraphSettleWatcher swap their parcelWatcher.subscribe call for the new module; callback bodies, ignore globs, and settle windows are untouched. UI5_WATCH_POLL_INTERVAL overrides the interval (clamped).

A mid-run EINTR death after a passing startup test is not covered; force UI5_WATCH_MODE=polling in that case.

Fixes: #1479

@RandomByte
RandomByte force-pushed the fix/fs-watch-in-containers branch 3 times, most recently from 6d87ab2 to 8aa2ff0 Compare August 11, 2026 11:43
In many container setups (Docker over overlayfs, bind mounts, network
filesystems) Linux inotify either does not deliver events or the native
@parcel/watcher thread dies on EINTR: a SIGCHLD from a child process
interrupts the blocking poll(), which the watcher treats as fatal (see
parcel-bundler/watcher issue #253). In both cases subscribe() resolves and
then goes silent, with no error to catch. The incremental build derives
"what changed" solely from watcher events, so a silent watcher breaks
rebuilds and live reload entirely.

Parcel offers no polling subscription (its brute-force backend is
query-only), so add one in a new module, pollingWatcher.js, that all three
watcher consumers call instead of @parcel/watcher directly. It exposes a
subscribe() matching @parcel/watcher's exact contract. The backend is chosen
once per process and memoized: UI5_WATCH_MODE=polling|native forces it,
otherwise a startup test subscribes natively to a temp dir on the same
filesystem as the real watch, writes a file, and falls back to polling if no
event arrives within 1500 ms.

The polling path walks the tree and diffs an mtimeMs+size snapshot every
250 ms (rescheduling itself after each walk so a slow crawl cannot overlap
the next), emitting the same {type, path} events the native backend would.
Errors flow through the callback so each consumer's existing recovery path
fires unchanged. The 250 ms interval stays below WATCHER_BURST_SETTLE_MS
(550 ms) so downstream event batching still holds. Ignore globs reuse
micromatch (already a direct dependency) and prune ignored directories so
node_modules is never crawled.

WatchHandler, ProjectDefinitionWatcher, and projectGraphSettleWatcher swap
their parcelWatcher.subscribe call for the new module; callback bodies,
ignore globs, and settle windows are untouched. UI5_WATCH_POLL_INTERVAL
overrides the interval (clamped).

A mid-run EINTR death after a passing startup test is not covered; force
UI5_WATCH_MODE=polling in that case.

Fixes: #1479
@RandomByte
RandomByte force-pushed the fix/fs-watch-in-containers branch from 8aa2ff0 to 60e08cb Compare August 11, 2026 11:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UI5 Build Cache does not work in case process is executed in a container

1 participant